/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.log4j;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.Writer;

import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.helpers.QuietWriter;
import org.apache.log4j.spi.ErrorCode;

// Contibutors: Jens Uwe Pipka <jens.pipka@gmx.de>
//              Ben Sandee

/**
 * FileAppender appends log events to a file.
 * 
 * <p>
 * Support for <code>java.io.Writer</code> and console appending has been
 * deprecated and then removed. See the replacement solutions:
 * {@link WriterAppender} and {@link ConsoleAppender}.
 * 
 * @author Ceki G&uuml;lc&uuml;
 * */
public class FileAppender extends WriterAppender {

	/**
	 * Controls file truncatation. The default value for this variable is
	 * <code>true</code>, meaning that by default a <code>FileAppender</code>
	 * will append to an existing file and not truncate it.
	 * 
	 * <p>
	 * This option is meaningful only if the FileAppender opens the file.
	 */
	protected boolean fileAppend = true;

	/**
	 * The name of the log file.
	 */
	protected String fileName = null;

	/**
	 * Do we do bufferedIO?
	 */
	protected boolean bufferedIO = false;

	/**
	 * Determines the size of IO buffer be. Default is 8K.
	 */
	protected int bufferSize = 8 * 1024;

	/**
	 * The default constructor does not do anything.
	 */
	public FileAppender() {
	}

	/**
	 * Instantiate a <code>FileAppender</code> and open the file designated by
	 * <code>filename</code>. The opened filename will become the output
	 * destination for this appender.
	 * 
	 * <p>
	 * If the <code>append</code> parameter is true, the file will be appended
	 * to. Otherwise, the file designated by <code>filename</code> will be
	 * truncated before being opened.
	 * 
	 * <p>
	 * If the <code>bufferedIO</code> parameter is <code>true</code>, then
	 * buffered IO will be used to write to the output file.
	 */
	public FileAppender(Layout layout, String filename, boolean append,
			boolean bufferedIO, int bufferSize) throws IOException {
		this.layout = layout;
		this.setFile(filename, append, bufferedIO, bufferSize);
	}

	/**
	 * Instantiate a FileAppender and open the file designated by
	 * <code>filename</code>. The opened filename will become the output
	 * destination for this appender.
	 * 
	 * <p>
	 * If the <code>append</code> parameter is true, the file will be appended
	 * to. Otherwise, the file designated by <code>filename</code> will be
	 * truncated before being opened.
	 */
	public FileAppender(Layout layout, String filename, boolean append)
			throws IOException {
		this.layout = layout;
		this.setFile(filename, append, false, bufferSize);
	}

	/**
	 * Instantiate a FileAppender and open the file designated by
	 * <code>filename</code>. The opened filename will become the output
	 * destination for this appender.
	 * 
	 * <p>
	 * The file will be appended to.
	 */
	public FileAppender(Layout layout, String filename) throws IOException {
		this(layout, filename, true);
	}

	/**
	 * The <b>File</b> property takes a string value which should be the name of
	 * the file to append to.
	 * 
	 * <p>
	 * <font color="#DD0044"><b>Note that the special values "System.out" or
	 * "System.err" are no longer honored.</b></font>
	 * 
	 * <p>
	 * Note: Actual opening of the file is made when {@link #activateOptions} is
	 * called, not when the options are set.
	 */
	public void setFile(String file) {
		// Trim spaces from both ends. The users probably does not want
		// trailing spaces in file names.
		String val = file.trim();
		fileName = val;
	}

	/**
	 * Returns the value of the <b>Append</b> option.
	 */
	public boolean getAppend() {
		return fileAppend;
	}

	/** Returns the value of the <b>File</b> option. */
	public String getFile() {
		return fileName;
	}

	/**
	 * If the value of <b>File</b> is not <code>null</code>, then
	 * {@link #setFile} is called with the values of <b>File</b> and
	 * <b>Append</b> properties.
	 * 
	 * @since 0.8.1
	 */
	public void activateOptions() {
		if (fileName != null) {
			try {
				setFile(fileName, fileAppend, bufferedIO, bufferSize);
			} catch (java.io.IOException e) {
				errorHandler.error("setFile(" + fileName + "," + fileAppend
						+ ") call failed.", e, ErrorCode.FILE_OPEN_FAILURE);
			}
		} else {
			// LogLog.error("File option not set for appender ["+name+"].");
			LogLog.warn("File option not set for appender [" + name + "].");
			LogLog.warn("Are you using FileAppender instead of ConsoleAppender?");
		}
	}

	/**
	 * Closes the previously opened file.
	 */
	protected void closeFile() {
		if (this.qw != null) {
			try {
				this.qw.close();
			} catch (java.io.IOException e) {
				if (e instanceof InterruptedIOException) {
					Thread.currentThread().interrupt();
				}
				// Exceptionally, it does not make sense to delegate to an
				// ErrorHandler. Since a closed appender is basically dead.
				LogLog.error("Could not close " + qw, e);
			}
		}
	}

	/**
	 * Get the value of the <b>BufferedIO</b> option.
	 * 
	 * <p>
	 * BufferedIO will significatnly increase performance on heavily loaded
	 * systems.
	 */
	public boolean getBufferedIO() {
		return this.bufferedIO;
	}

	/**
	 * Get the size of the IO buffer.
	 */
	public int getBufferSize() {
		return this.bufferSize;
	}

	/**
	 * The <b>Append</b> option takes a boolean value. It is set to
	 * <code>true</code> by default. If true, then <code>File</code> will be
	 * opened in append mode by {@link #setFile setFile} (see above). Otherwise,
	 * {@link #setFile setFile} will open <code>File</code> in truncate mode.
	 * 
	 * <p>
	 * Note: Actual opening of the file is made when {@link #activateOptions} is
	 * called, not when the options are set.
	 */
	public void setAppend(boolean flag) {
		fileAppend = flag;
	}

	/**
	 * The <b>BufferedIO</b> option takes a boolean value. It is set to
	 * <code>false</code> by default. If true, then <code>File</code> will be
	 * opened and the resulting {@link java.io.Writer} wrapped around a
	 * {@link BufferedWriter}.
	 * 
	 * BufferedIO will significatnly increase performance on heavily loaded
	 * systems.
	 */
	public void setBufferedIO(boolean bufferedIO) {
		this.bufferedIO = bufferedIO;
		if (bufferedIO) {
			immediateFlush = false;
		}
	}

	/**
	 * Set the size of the IO buffer.
	 */
	public void setBufferSize(int bufferSize) {
		this.bufferSize = bufferSize;
	}

	/**
	 * <p>
	 * Sets and <i>opens</i> the file where the log output will go. The
	 * specified file must be writable.
	 * 
	 * <p>
	 * If there was already an opened file, then the previous file is closed
	 * first.
	 * 
	 * <p>
	 * <b>Do not use this method directly. To configure a FileAppender or one of
	 * its subclasses, set its properties one by one and then call
	 * activateOptions.</b>
	 * 
	 * @param fileName
	 *            The path to the log file.
	 * @param append
	 *            If true will append to fileName. Otherwise will truncate
	 *            fileName.
	 */
	public synchronized void setFile(String fileName, boolean append,
			boolean bufferedIO, int bufferSize) throws IOException {
		LogLog.debug("setFile called: " + fileName + ", " + append);

		// It does not make sense to have immediate flush and bufferedIO.
		if (bufferedIO) {
			setImmediateFlush(false);
		}

		reset();
		FileOutputStream ostream = null;
		try {
			//
			// attempt to create file
			//
			ostream = new FileOutputStream(fileName, append);
		} catch (FileNotFoundException ex) {
			//
			// if parent directory does not exist then
			// attempt to create it and try to create file
			// see bug 9150
			//
			String parentName = new File(fileName).getParent();
			if (parentName != null) {
				File parentDir = new File(parentName);
				if (!parentDir.exists() && parentDir.mkdirs()) {
					ostream = new FileOutputStream(fileName, append);
				} else {
					throw ex;
				}
			} else {
				throw ex;
			}
		}
		Writer fw = createWriter(ostream);
		if (bufferedIO) {
			fw = new BufferedWriter(fw, bufferSize);
		}
		this.setQWForFiles(fw);
		this.fileName = fileName;
		this.fileAppend = append;
		this.bufferedIO = bufferedIO;
		this.bufferSize = bufferSize;
		writeHeader();
		LogLog.debug("setFile ended");
	}

	/**
	 * Sets the quiet writer being used.
	 * 
	 * This method is overriden by {@link RollingFileAppender}.
	 */
	protected void setQWForFiles(Writer writer) {
		this.qw = new QuietWriter(writer, errorHandler);
	}

	/**
	 * Close any previously opened file and call the parent's <code>reset</code>
	 * .
	 */
	protected void reset() {
		closeFile();
		this.fileName = null;
		super.reset();
	}
}
